package com.njuse.jvmfinal.loading;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import com.njuse.jvmfinal.Starter;
import com.njuse.jvmfinal.classloader.classfileparser.ClassFile;
import com.njuse.jvmfinal.memory.jclass.AccessFlags;
import com.njuse.jvmfinal.memory.jclass.Field;
import com.njuse.jvmfinal.memory.jclass.JClass;
import com.njuse.jvmfinal.memory.jclass.Method;
import com.njuse.jvmfinal.memory.jclass.runtimeConstantPool.RuntimeConstantPool;
import com.njuse.jvmfinal.memory.jclass.runtimeConstantPool.constant.wrapper.DoubleWrapper;
import com.njuse.jvmfinal.memory.jclass.runtimeConstantPool.constant.wrapper.FloatWrapper;
import com.njuse.jvmfinal.memory.jclass.runtimeConstantPool.constant.wrapper.IntWrapper;
import com.njuse.jvmfinal.memory.jclass.runtimeConstantPool.constant.wrapper.LongWrapper;
import com.njuse.jvmfinal.util.IOUtil;

public class loader {
	private static loader loading=null;
	private String JAVA_HOME;
	private String bootpath;
	private String extpath;
	
	public loader() {

			JAVA_HOME=System.getenv("JAVA_HOME");
			
		    bootpath=JAVA_HOME+File.separator+"jre"+File.separator+"lib";
			bootpath=bootpath.replace("\\", File.separator);
			
			extpath=JAVA_HOME+File.separator+"jre"+File.separator+"lib"+File.separator+"ext";
			extpath=extpath.replace("\\", File.separator);
			
			
	}
	//单例
	public static loader getinstance() {
		if (loading==null) {
			loading=new loader();			
		}
		return loading;
	}
	
	
    public JClass loadarray(String name) {
    	
    	if (methodarea.getinstance().findarrayclass(name)!=null) {
    		return methodarea.getinstance().findarrayclass(name);
    	}
    	char[] charname=name.toCharArray();
    	JClass clazz=null;
    	
    	switch (charname[1]) {
    		case 'L':{
        		String objectname=name.substring(2, name.length()-1);
        		JClass component=load(objectname,Starter.startcp,1);
        		clazz=new JClass(name,component);
        		methodarea.getinstance().addarrayclass(clazz);
        	    break;
    		         }
    		case '[':{
        		String lessarray=name.substring(1,name.length());
        		JClass component=load(lessarray,Starter.startcp,1);
        		clazz=new JClass(name,component);
        		methodarea.getinstance().addarrayclass(clazz);
        		break;
    		}
        	default:{
        		String comname=name.substring(1, name.length());
        		clazz=new JClass(name,comname);
        		methodarea.getinstance().addarrayclass(clazz);
        	    break;
        	}
    	}
    		
    	return clazz;
    }
    
    
	public JClass load(String name,String path,int prefer) {
		
		if (name.startsWith("[")) {
			return loadarray(name);
		}
		
    	name=name.replace(".", File.separator);
    	name=name.replace("/", File.separator);		
    	name=name+".class";
    	if (methodarea.getinstance().findclass(name)!=null) {
    		return methodarea.getinstance().findclass(name);
    	}
    	
    	
    //双亲委托！！	
		byte[] finding;
		int flag=0;
		
		
		try {
			finding=findwildentry(bootpath,name);
			if (finding!=null) flag=3;
		}catch(Exception e) {
			finding=null;
		}

		
		if (finding==null&&prefer<=2) {
			try {	
				finding=findwildentry(extpath,name); 
				if (finding!=null) flag=2;
			}catch(Exception e) {
				finding=null;
			}
		}
		
		if (finding==null&&prefer==1) {
			String temp=path+File.separator+name;
			try {			
				finding=finddirentry(temp); 
				if (finding!=null) flag=1;
			}catch(Exception e) {
				finding=null;
			}
		}
		
	    if (finding==null) throw new RuntimeException();
		
		 ClassFile classFile=new ClassFile(finding);
	     JClass clazz=new JClass(classFile);
	     clazz.setPreference(flag);
	     
	   //loading superclass  
	     if (!clazz.getSuperClassName().equals("")) {
	    	JClass superclazz = this.load(clazz.getSuperClassName(), path, clazz.getPreference());
	    	clazz.setSuperClass(superclazz);
	     }

	   //loading interfaces  
	     int l=clazz.getInterfaceNames().length;
	     JClass[] interfaces=new JClass[l];
	     for (int i=0;i<l;i++) {
	    	 interfaces[i]=this.load(clazz.getInterfaceNames()[i], path, clazz.getPreference());
	     }
		 clazz.setInterfaces(interfaces);
		 
	
		 //焊接部分
		 int count=0;
		 if (clazz.getSuperClass()!=null) {
			 count=clazz.getSuperClass().getInstanceSlotCount();
		 }
		 Field[] fields=clazz.getFields();
		 for (int i=0;i<fields.length;i++) {
			 if ((fields[i].accessFlags&AccessFlags.ACC_STATIC)==0) {
				 fields[i].setPosition(count);
				 count++;
				 if (fields[i].descriptor.equals("J")||fields[i].descriptor.equals("D")) {
					 count++;
				 }
			 }
		 }
		 clazz.setInstanceSlotCount(count);
				 
		 
		 //梅开二度
		 count=0;
		 fields=clazz.getFields();
		 for (int i=0;i<fields.length;i++) {
			 if ((fields[i].accessFlags&AccessFlags.ACC_STATIC)!=0) {
				 fields[i].setPosition(count);
				 count++;
				 if (fields[i].descriptor.equals("J")||fields[i].descriptor.equals("D")) {
					 count++;
				 }
			 }
		 }
		 clazz.setStaticSlotCount(count);
		 Object[] statics=clazz.getStatics();
		 for (int i=0;i<fields.length;i++) {
			 if ((fields[i].accessFlags&AccessFlags.ACC_STATIC)!=0) {
				 int position=fields[i].getPosition();
				 String x=fields[i].descriptor;
				 RuntimeConstantPool rtcp=clazz.getRuntimeConstantPool();
				 int index=fields[i].getConstValueIndex();
				 switch (x) {
				     case "I":
				     case "Z":
				     case "S":
				     case "C":
				     case "B":
				    	 if ((fields[i].accessFlags&AccessFlags.ACC_FINAL)!=0) {
				     		IntWrapper xxint=(IntWrapper) rtcp.getConstant(index);
				     		statics[position]=xxint.getValue();
				    	 }else {
				    		 statics[position]=0;			    	
				    	 }
				    	 break;
				     case "F":
				    	 if ((fields[i].accessFlags&AccessFlags.ACC_FINAL)!=0) {
					     		FloatWrapper xxfloat=(FloatWrapper) rtcp.getConstant(index);
					     		statics[position]=Float.floatToIntBits(xxfloat.getValue());
					    	 }else {
					    		statics[position]=0;			    	
					    	 }
					    	 break; 
				     case "J":
				    	 if ((fields[i].accessFlags&AccessFlags.ACC_FINAL)!=0) {
					     		LongWrapper xxlong=(LongWrapper) rtcp.getConstant(index);
					     		long temp=xxlong.getValue();
					     		statics[position]=(int)temp;
					     		statics[position+1]=(int)(temp>>32);
					    	 }else {
					    		statics[position]=0;	
					    		statics[position]=0;
					    	 }
					    	 break;  
				     case "D":
				    	 if ((fields[i].accessFlags&AccessFlags.ACC_FINAL)!=0) {
					     		DoubleWrapper xxdouble=(DoubleWrapper) rtcp.getConstant(index);
					     		long temp=Double.doubleToLongBits(xxdouble.getValue());
					     		statics[position]=(int)temp;
					     		statics[position+1]=(int)(temp>>32);
					    	 }else {
					    		statics[position]=0;	
					    		statics[position]=0;
					    	 }
					    	 break;  
					 default:
						 statics[position]=null;
				 }
			 }
		 }
		clazz.setStatics(statics);
		 		 
		 
		clazz.setLoadingok(true); 
		methodarea.getinstance().addclass(clazz); 
		return clazz;
	}
	
   public byte[] finddirentry(String finalpath) throws Exception {
	   
	   FileInputStream inputStream=new FileInputStream(finalpath);
	   byte[] source=IOUtil.readFileByBytes(inputStream);
	   if (source==null) throw new Exception();
	   return source;
   }
   
   public byte[] findwildentry(String path,String name) throws Exception{
    File file=new File(path);
   	File[] files = file.listFiles();
   	for (int i=0;i<files.length;i++) {
   		if (files[i].getPath().endsWith("jar")||files[i].getPath().endsWith("JAR")) {
   			
   			String jarName =files[i].getPath();
   	        JarFile jarFile = new JarFile(jarName);
   	        
   			Enumeration<JarEntry> entrys = jarFile.entries();
   			while (entrys.hasMoreElements()) {
   				JarEntry jarEntry = entrys.nextElement();
   				String temp=jarEntry.getName();  				
   			    temp=temp.replace("/", File.separator);
   				if (temp.contains(name)) {
   				   	try {
   				   		InputStream inputStream =jarFile.getInputStream(jarEntry);
   				        return IOUtil.readFileByBytes(inputStream);}
   				    	catch(IOException e) {
   				    		return null;
   				        }
   			    }
   			}
   		}
   	}
   	return null;
   }
	
}
